package org.dlsu.mt.parser.generator;

import java.util.Vector;

/**
 * This class represents a rule or a production from a grammar.
 * It contains the left hand side symbol, the list of symbols from the right hand side
 * and list of features associated with each RHS symbol.
 */
public class Rule {
	/**
	 * The left hand side of the production
	 */
	protected String lhs;
	/**
	 * The symbols at the right hand side of the production
	 */
	protected Vector rhs;
	/**
	 * The features associated with the symbols on the right hand side
	 */
	protected Vector f;
    /**
     * The dot value represents the right term of the dot used in creating the parse table.
     * dot-1 will represent the left term of the dot
     */
	protected int dot=0;

	/**
	 * Creates a new rule with the corresponding left hand side symbol
	 * @param lhs The left hand side symbol
	 */
	public Rule(String lhs) {
		this.lhs = lhs;
		rhs = new Vector();
		f = new Vector();
	}

	/**
	 * Creates a new rule with the corresponding left hand side symbol, right hand side symbols, features, and the position of the dot
	 *
	 * @param lhs The left hand side symbol
	 * @param rhs The list or vector of the symbols in the right-hand side in order
	 * @param f The list or vector of features associated with each symbol in the right-hand side
	 * @param dot The position of the dot in the rule
	 */
	private Rule(String lhs, Vector rhs, Vector f, int dot){
		this.lhs = lhs;
		this.rhs =rhs;
		this.f =f;
		this.dot = dot;
	}

	/**
	 * Add a symbol to the end of the right-hand side of the rule
	 *
	 * @param term The symbol to add on the right-hand side
	 */
	public void addRHSTerm(String term) {
		rhs.add(term);
	}

	/**
	 * Add a feature associated at the last right-hand side
	 *
	 * @param feature The feature to add
	 */
	public void addRHSFeature(String feature){
		f.add(feature);
	}

	/**
	 * Add a symbol to the end of the right-hand side of the rule and its associated feature
	 *
	 * @param term The symbol to add on the right-hand side
	 * @param feature The feature to add
	 */
	public void addRHS(String term, String feature) {
		rhs.add(term);
		f.add(feature);
	}

	/**
	 * Gets the term at ith term on the right-hand side
	 *
	 * @param index The index of the term to be retrieved
	 * @return The term at index on the right-hand side
	 */
	public String getRHSTerm(int index) {
		if (index < f.size())
			return (String) rhs.elementAt(index);
		return null;
	}

	/**
	 * Gets the symbol at the right side of the dot returns null if none
	 *
	 * @return Symbol at the right of the dot
	 */
	public String getRightOfDot() {
		if (dot < f.size())
			return (String) rhs.elementAt(dot);
		return null;
	}

	/**
	 * Gets the symbol at the left of the dot returns null if none
     *
     * @return Symbol at the left of the dot
	 */
	public String getLeftOfDot() {
		if (dot <= 0)
			return (String) rhs.elementAt(dot-1);
		return null;
	}

	/**
	 * Gets the total number of symbols at the right-hand side
	 *
	 * @return The number of symbols at the right-hand side
	 */
	public int getRHSTermSize(){
		return rhs.size();
	}

	/**
	 * Gets the total number of features at the right-hand side
	 *
	 * @return The number of features at the right-hand side
	 */
	public int getFeatureSize(){
		return f.size();
	}

	/**
	 * Gets the position of the dot which is in between getRHSTerm(dot-1) and getRHSTerm(dot)
	 *
	 * @return The position of the dot
	 */
	public int getDot() {
		return dot;
	}

	/**
	 * Gets the feature of the ith symbol
	 *
	 * @param index The index of the feature to retrieve
	 * @return The feature at the index
	 */
	public String getFeature(int index) {
		if (index >= f.size() || index < 0)
			return null;
		return (String) f.elementAt(index);
	}

	/**
	 * Moves the dot one position to the right
	 *
	 * @return A new instance of the rule with the dot moved
	 */
	public Rule moveDot() {
		if (dot + 1 <= f.size())
			return new Rule(lhs,rhs,f,dot+1);
		return null;
	}

	/**
	 * Returns the string representation of the rule with the dot as follows:
	 * 		<Left hand side symbol> = @ <Right-hand side symbol 1> *[<feature 1>]* <Right-hand side symbol 2> *[<feature 2>]* ... <Right-hand side symbol N> *[<feature N>]*
	 * @ is the position of the dot
	 *
	 * @return String representation of the rule with the dot
	 */
	public String toString() {
		StringBuffer sb = new StringBuffer(20);
		sb.append(lhs+" = ");
		for(int i=0;i<f.size();i++)
			if (dot != i)
				sb.append(rhs.elementAt(i).toString() + " *[" + f.elementAt(i).toString() + "]* ");
			else
				sb.append(" @ "+rhs.elementAt(i).toString() + " *[" + f.elementAt(i).toString() + "]* ");
		if(dot == f.size())
			sb.append(" @ ");

		return sb.toString();
	}

	/**
	 * Returns the string representation of the rule without the dot as follows:
	 * 		<Left hand side symbol> = <Right-hand side symbol 1> *[<feature 1>]* <Right-hand side symbol 2> *[<feature 2>]* ... <Right-hand side symbol N> *[<feature N>]*
	 *
	 * @return String representation of the rule without the dot
	 */
	public String toStringNoDot() {
		StringBuffer sb = new StringBuffer(20);
		sb.append(lhs+" = ");
		for(int i=0;i<f.size();i++)
			sb.append(rhs.elementAt(i).toString() + " *[" + f.elementAt(i).toString() + "]* ");

		return sb.toString();
	}

	/**
	 * Returns the string representation of the rule without the dot and the feature associated as follows:
	 * 		<Left hand side symbol> = <Right-hand side symbol 1> <Right-hand side symbol 2> ... <Right-hand side symbol N>
	 *
	 * @return String representation of the rule without the dot
	 */
	public String toStringNoDotNoFeature() {
		StringBuffer sb = new StringBuffer(20);
		sb.append(lhs+" = ");
		for(int i=0;i<f.size();i++)
			sb.append(rhs.elementAt(i).toString());

		return sb.toString();
	}

	/**
	 * Returns the string representation of the rule with the dot but without the feature associated as follows:
	 * 		<Left hand side symbol> = @ <Right-hand side symbol 1> <Right-hand side symbol 2> ... <Right-hand side symbol N>
	 * @ is the position of the dot
	 *
	 * @return String representation of the rule without the dot
	 */
	public String toStringNoFeature() {
		StringBuffer sb = new StringBuffer(20);
		sb.append(lhs+" = ");
		for(int i=0;i<f.size();i++)
			if (dot != i)
				sb.append(rhs.elementAt(i).toString());
			else
				sb.append(" @ "+rhs.elementAt(i).toString());
		if(dot == f.size())
			sb.append(" @ ");

		return sb.toString();
	}

	/**
	 * Returns the string representation of the rule in XML form as follow:
	 * <PRE>
	 * 		<RULE>
	 *			<LHS> <Left hand side> </LHS>
	 *			<RHS>
	 *				<TERM order = 0>
	 *					<SYMBOL> <The RHS Symbol> </SYMBOL>
	 *					<FEATURE> <The RHS Feature> </FEATURE>
	 *				</TERM>
	 *				<TERM order = 1>
	 *					<SYMBOL> <The RHS Symbol> </SYMBOL>
	 *					<FEATURE> <The RHS Feature> </FEATURE>
	 *				</TERM>
	 *				<TERM order = 2>
	 *					<SYMBOL> <The RHS Symbol> </SYMBOL>
	 *					<FEATURE> <The RHS Feature> </FEATURE>
	 *				</TERM>
	 *				...
	 *				<TERM order = <nth term in the RHS>>
	 *					<SYMBOL> <The RHS Symbol> </SYMBOL>
	 *					<FEATURE> <The RHS Feature> </FEATURE>
	 *				</TERM>
	 *			</RHS>
	 *		</RULE>
	 * </PRE>
	 *
	 * @return XML representation of the rule
	 */
	public String toXML() {
		StringBuffer sb = new StringBuffer(20);
		sb.append("<RULE><LHS>"+lhs+"</LHS><RHS>");
		for(int i=0;i<f.size();i++)
			sb.append("<TERM order=\""+i+"\"> <SYMBOL>"+rhs.elementAt(i).toString() + "</SYMBOL> <FEATURE>" + f.elementAt(i).toString() + "</FEATURE></TERM>");
		sb.append("</RHS></RULE>");
		return sb.toString();
	}

	/**
	 * Returns the hashcode of the string representation with the dot
	 *
	 * @return Hashcode of the string representation with the dot
	 */
	public int hashCode() {
		return toString().hashCode();
	}

	/**
	 * Compares the two Rules considering the position of the dot
	 *
	 * @param obj The rule to compare with the current object
	 * @return True if both rules are the same, false otherwise
	 */
	public boolean equals(Object obj) {
		int i;
		boolean same = true;
		Rule target = (Rule) obj;

		if (!target.lhs.equals(lhs))
			return false;

		if (target.rhs.size() != rhs.size())
			return false;

		if (target.f.size() != f.size())
			return false;

		if (target.dot != dot)
			return false;

		for(i=0;i<rhs.size();i++) {
			same = same && rhs.elementAt(i).equals(target.rhs.elementAt(i)) && f.elementAt(i).equals(target.f.elementAt(i));
			if (!same)
				return false;
		}

		return true;
	}

	/**
	 * Compares the two Rules without considering the position of the dot
	 *
	 * @param obj The rule to compare with the current object
	 * @return True if both rules are the same, false otherwise
	 */
	public boolean equalsNoDot(Object obj) {
		int i;
		boolean same = true;
		Rule target = (Rule) obj;

		if (!target.lhs.equals(lhs))
			return false;

		if (target.rhs.size() != rhs.size())
			return false;

		if (target.f.size() != f.size())
			return false;

		for(i=0;i<rhs.size();i++) {
			same = same && rhs.elementAt(i).equals(target.rhs.elementAt(i)) && f.elementAt(i).equals(target.f.elementAt(i));
			if (!same)
				return false;
		}

		return true;
	}

	/**
	 * Gets the symbol on the left-hand side of the rule/production
	 *
	 * @return Symbol at the left-hand side of the rule
	 */
	public String getLHS() {
		return lhs;
	}

	/**
	 * Compares if the left-hand side symbol is the same as the given string
	 *
	 * @param s The left-hand side string to compare with
	 * @return True if the given left-side symbol is equal, false otherwise
	 */
	public boolean isLHSEqual(String s) {
		return s.trim().toUpperCase().equalsIgnoreCase(lhs.trim().toUpperCase());
	}
}